package test.cert;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;

import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

/**
 * 根证书生成器
 * @author shawn
 *
 */
@SuppressWarnings("deprecation")
public class CertGen
{
	/**
	 * @param args
	 * @throws NoSuchAlgorithmException 
	 * @throws InvalidAlgorithmParameterException 
	 * @throws SignatureException 
	 * @throws IllegalStateException 
	 * @throws InvalidKeyException 
	 * @throws NoSuchProviderException 
	 * @throws CertificateException 
	 * @throws KeyStoreException 
	 * @throws IOException 
	 */
	public static void main(String[] args) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalStateException, SignatureException, NoSuchProviderException, CertificateException, KeyStoreException, IOException
	{
		//	设置发行者和签名对象信息
		Hashtable<DERObjectIdentifier, String> issuerTable = new Hashtable<DERObjectIdentifier, String>();
		issuerTable.put(X509Principal.CN, "Nook Info");
		issuerTable.put(X509Principal.OU, "abettor.org");
		issuerTable.put(X509Principal.O, "abettor");
		issuerTable.put(X509Principal.L, "Nankai");
		issuerTable.put(X509Principal.ST, "Tianjin");
		issuerTable.put(X509Principal.C, "CN");
		issuerTable.put(X509Principal.E, "shotting@gmail.com");
		Vector<DERObjectIdentifier> issuerOrder = new Vector<DERObjectIdentifier>();
		issuerOrder.add(X509Principal.CN);
		issuerOrder.add(X509Principal.OU);
		issuerOrder.add(X509Principal.O);
		issuerOrder.add(X509Principal.L);
		issuerOrder.add(X509Principal.ST);
		issuerOrder.add(X509Principal.C);
		issuerOrder.add(X509Principal.E);
		X509Name issuer = new X509Name(issuerOrder, issuerTable);
		X509Name subject = issuer;

		//	计算有效期
		Calendar cal = Calendar.getInstance();
		Date validTime = cal.getTime();
		cal.add(Calendar.YEAR, 5);
		Date expireTime = cal.getTime();

		//	根据指定算法生成密钥对
		Security.addProvider(new BouncyCastleProvider());
		KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
		kpg.initialize(new RSAKeyGenParameterSpec(1024, RSAKeyGenParameterSpec.F4), new SecureRandom());
		KeyPair keyPair = kpg.generateKeyPair();

		//	设定证书生成器
		X509V3CertificateGenerator cg = new X509V3CertificateGenerator();
		cg.setIssuerDN(issuer);
		cg.setNotAfter(expireTime);
		cg.setNotBefore(validTime);
		cg.setPublicKey(keyPair.getPublic());
		cg.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
		cg.setSignatureAlgorithm("SHA1withRSA");
		cg.setSubjectDN(subject);
		cg.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(true));
		cg.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keyPair.getPublic()));
		cg.addExtension(X509Extensions.KeyUsage, false, new KeyUsage(KeyUsage.cRLSign | KeyUsage.dataEncipherment | KeyUsage.digitalSignature | KeyUsage.keyAgreement | KeyUsage.keyCertSign | KeyUsage.keyEncipherment | KeyUsage.nonRepudiation));

		//	生成并验证证书
		X509Certificate cert = cg.generate(keyPair.getPrivate(), new SecureRandom());
		PKCS12BagAttributeCarrier bagCarrier = (PKCS12BagAttributeCarrier) cert;
		bagCarrier.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(issuerTable.get(X509Principal.CN)));
		bagCarrier.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, new SubjectKeyIdentifierStructure(keyPair.getPublic()));
		System.out.print(cert);
		cert.checkValidity();
		cert.verify(keyPair.getPublic());

		//	保存证书（私钥）
		File file = new File(CertGen.class.getResource("").getPath() + "NookInfo.pfx");
		if(file.exists())
		{
			file.delete();
		}
		file.createNewFile();
		OutputStream os = new FileOutputStream(file);
		KeyStore ks = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
		ks.load(null);
		ks.setKeyEntry("Nook Info", keyPair.getPrivate(), "123456".toCharArray(), new Certificate[]{cert});
		ks.store(os, "123456".toCharArray());
		os.close();

		//	保存证书（公钥）
		int lineWidth = 76;
		ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
		DEROutputStream dos = new DEROutputStream(baos);
		dos.write(Base64.encode(cert.getEncoded()));
		dos.close();
		byte[] bytes = baos.toByteArray();
		int line = bytes.length / lineWidth;
		if(bytes.length % lineWidth > 0)
		{
			line ++;
		}
		byte[] crlf = new byte[]{0x0D, 0x0A};
		file = new File(CertGen.class.getResource("").getPath() + "NookInfo.cer");
		if(file.exists())
		{
			file.delete();
		}
		file.createNewFile();
		os = new FileOutputStream(file);
		os.write("-----BEGIN CERTIFICATE-----".getBytes());
		os.write(crlf);
		for(int i = 0; i < line; i ++)
		{
			int offset = i * lineWidth;
			os.write(bytes, offset, bytes.length - offset >= lineWidth ? lineWidth : bytes.length - offset);
			os.write(crlf);
		}
		os.write("-----END CERTIFICATE-----".getBytes());
		os.close();
	}
}
